package com.gitee.zhangchenyan.takin.service.common;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.gitee.zhangchenyan.takin.common.utils.ExceptionUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.logging.LogLevel;
import org.springframework.core.ResolvableType;

import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Deacription TODO
 * @Author Administrator
 * @Date 2021/12/27 10:04
 * @Version 1.0
 **/
@Slf4j
public class TakinTypeToken {

    public List<TakinType> takinTypeList = new ArrayList<>();
    public List<String> pidList = new ArrayList<>();
    public Map<String, ParameterizedTypeImpl> parameterizedTypeMap = new HashMap<>();


    public Type getParameterizedTypeImpl(ResolvableType resolvableType) {
        getTypes(resolvableType);

        if (takinTypeList.size() == 1) {
            return getBasicType(takinTypeList.get(0).getName());
        }

        for (TakinType takinType : takinTypeList) {
            if (!pidList.contains(takinType.getPid()) && !StrUtil.isEmpty(takinType.getPid())) {
                pidList.add(takinType.getPid());
            }
        }
        ParameterizedTypeImpl parameterizedType = null;
        for (int i = pidList.size() - 1; i >= 0; i--) {
            String pid = pidList.get(i);
            List<TakinType> collectTypes = takinTypeList.stream().filter(t -> t.getPid() == pid).collect(Collectors.toList());
            TakinType pidType = takinTypeList.stream().filter(t -> t.getId() == pid).collect(Collectors.toList()).get(0);
            if (collectTypes.size() == 1) {
                Type actualType = getActualType(collectTypes.get(0));
                parameterizedType = new ParameterizedTypeImpl(new Type[]{actualType}, null, getBasicType(pidType.getName()));
                parameterizedTypeMap.put(pid, parameterizedType);
            } else {
                Type[] actualTypes = new Type[collectTypes.size()];
                for (int j = 0; j <= collectTypes.size() - 1; j++) {
                    actualTypes[j] = getActualType(collectTypes.get(j));
                }
                parameterizedType = new ParameterizedTypeImpl(actualTypes, null, getBasicType(pidType.getName()));
                parameterizedTypeMap.put(pid, parameterizedType);
            }
        }
        takinTypeList.clear();
        pidList.clear();
        parameterizedTypeMap.clear();
        return parameterizedType;
    }

    private Type getActualType(TakinType takinType) {
        if (parameterizedTypeMap.containsKey(takinType.getId())) {
            return parameterizedTypeMap.get(takinType.getId());
        } else {
            return getBasicType(takinType.getName());
        }
    }

    private void getTypes(ResolvableType resolvableType) {
        getTypes(resolvableType, 0, "");
    }

    private void getTypes(ResolvableType resolvableType, int number, String pid) {
        try {
            String id = IdUtil.simpleUUID();
            TakinType takinType = new TakinType();
            takinType.setId(id);
            takinType.setPid(pid);
            takinType.setName(resolvableType.resolve().getName());
            takinType.setNumber(number);
            takinTypeList.add(takinType);
            if (resolvableType.hasGenerics()) {
                ResolvableType[] resolvableTypeGenerics = resolvableType.getGenerics();
                for (int i = 0; i <= resolvableTypeGenerics.length - 1; i++) {
                    getTypes(resolvableTypeGenerics[i], i, id);
                }
            }
        } catch (Exception ex) {
            ExceptionUtils.outputStr(ex,"TakinTypeToken",LogLevel.WARN);
        }

    }

    @SneakyThrows
    private Class<?> getBasicType(String type) {
        switch (type) {
            case "boolean":
                return Boolean.TYPE;
            case "int":
                return Integer.TYPE;
            case "long":
                return Long.TYPE;
            case "double":
                return Double.TYPE;
            case "float":
                return Float.TYPE;
            case "short":
                return Short.TYPE;
            case "byte":
                return Byte.TYPE;
            case "char":
                return Character.TYPE;
            default:
                return Class.forName(type);
        }
    }
}
